#ifndef __MMMConverterConfiguration_H_
#define __MMMConverterConfiguration_H_

#include <MMM/Motion/Legacy/Converter/ConverterFactory.h>

#include <boost/extension/shared_library.hpp>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <filesystem>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>

#include <string>
#include <iostream>
#include <VirtualRobot/RuntimeEnvironment.h>

// setup for converter plugin search, other search paths can be added with the command line parameter "libPath"
#ifdef WIN32
#define MMMConverter_LIB_EXTENSION ".dll"
#ifdef _DEBUG
#define MMMConverter_STANDARD_LIB_SEARCH_PATH MMMConverter_BASE_DIR "/../build/bin/Debug"
#else
#define MMMConverter_STANDARD_LIB_SEARCH_PATH MMMConverter_BASE_DIR "/../build/bin/Release"
#endif
#else
#define MMMConverter_LIB_EXTENSION ".so"
#define MMMConverter_STANDARD_LIB_SEARCH_PATH MMMConverter_BASE_DIR "/../build/lib/"
#endif

/*!
    Configuration of command line converter.
    By default some standard parameters are set.
*/
struct MMMConverterConfiguration
{
    //! Initialize with standard parameter set
    MMMConverterConfiguration()
    {
        converterMode = eHuman2MMM; // only this mode is supported so far (mmm->robot htb)

        converterName = "ConverterVicon2MMM";
        converterConfigFile = std::string(MMMConverter_BASE_DIR)+std::string("/../data/ConverterVicon2MMM_WinterConfig.xml");
        outModelFile = std::string(MMMConverter_BASE_DIR)+std::string("/../data/Model/Winter/mmm.xml");
        modelProcessorName = "Winter";
        modelProcessorConfigFile = std::string(MMMConverter_BASE_DIR)+std::string("/../data/ModelProcessor_Winter_1.79.xml");
        viconInputFile = std::string(MMMConverter_BASE_DIR)+std::string("/../data/WalkingStraightForward07.c3d");
        outFile = "converter_output.xml";
        std::cout << "MMMConverterBase: " << MMMConverter_STANDARD_LIB_SEARCH_PATH << std::endl;

        // init with standard lib path
        std::string testLibPath(MMMConverter_STANDARD_LIB_SEARCH_PATH);
        converterLibSearchPaths.push_back(testLibPath);
    converterLibSearchPaths.push_back(std::string(MMMTools_LIB_DIR)); // Variable defined in MMMToolsConfig.cmake. Use ADD_DEFINITIONS() in your CMakeList.txt to make this path available at compile time.
    }


    // By going through all plugin libraries and initializing them, the ConverterFactories are available for later usage
    std::vector<MMM::ConverterFactoryPtr> checkForFactories(std::vector<std::string> &libPaths)
    {
        std::vector<MMM::ConverterFactoryPtr> result;

        for (size_t i = 0; i < libPaths.size(); i++)
        {

            std::string lp = libPaths[i];
            std::filesystem::path targetDir(lp);
            bool isFile = false;

            try
            {
                isFile = is_regular_file(targetDir);
            } catch (...)
            {
            }
            if (isFile)
            {
                MMM_ERROR << "Path '" <<  lp << "' is not a directory" << std::endl;
                continue;
            }
            try
            {
                std::filesystem::directory_iterator it(targetDir), eod;
                std::filesystem::path libPath;
                //look through all files in MMMConverter_STANDARD_LIB_SEARCH_PATH
                BOOST_FOREACH(std::filesystem::path const &p, std::make_pair(it, eod))
                {
                    //MMM_INFO << "checking " << p.string() << std::endl;
                    if (p.extension().string() == MMMConverter_LIB_EXTENSION && is_regular_file(p))
                    {

                        boost::extensions::shared_library lib(p.string());
                        if (lib.open())
                        {
                            boost::function<boost::shared_ptr<MMM::ConverterFactory>()> getFactory;
                            getFactory = lib.get<boost::shared_ptr<MMM::ConverterFactory> >("getFactory");
                            if (getFactory)
                            {
                                MMM::ConverterFactoryPtr converterFactory = getFactory();
                                result.push_back(converterFactory);
                            }
                        }
                        else
                        {
                            MMM_INFO << "Could not open lib " << p.string()  << std::endl;

                        }
                    }

                }
            } catch (...)
            {
            }
        }
        return result;
    }

    void setupConverterFactories()
    {
        // The factory pointers are not explicitly needed, but we need to load all plugins, so that the static AbstractFactory methods are available
        std::vector<MMM::ConverterFactoryPtr> factories = checkForFactories(converterLibSearchPaths);
        std::vector< std::string > factoryList = MMM::ConverterFactory::getSubclassList();
        MMM_INFO << "Available ConverterFactories: " << std::endl;
        for (size_t i = 0; i < factoryList.size(); i++)
        {
            std::cout << " * " << factoryList[i] << std::endl;
        }
    }

    //! checks for command line parameters and updates configuration accordingly.
    bool processCommandLine(int argc, char *argv[])
    {
        VirtualRobot::RuntimeEnvironment::considerKey("outputModel");
        VirtualRobot::RuntimeEnvironment::considerKey("outputModelProcessor");
        VirtualRobot::RuntimeEnvironment::considerKey("outputModelProcessorConfigFile");
        VirtualRobot::RuntimeEnvironment::considerKey("inputModel");
        //VirtualRobot::RuntimeEnvironment::considerKey("inputModelProcessor");
        //VirtualRobot::RuntimeEnvironment::considerKey("inputModelProcessorConfigFile");
        VirtualRobot::RuntimeEnvironment::considerKey("inputDataVicon");
        VirtualRobot::RuntimeEnvironment::considerKey("inputDataMMM");
        VirtualRobot::RuntimeEnvironment::considerKey("libPath");
        VirtualRobot::RuntimeEnvironment::considerKey("converter");
        VirtualRobot::RuntimeEnvironment::considerKey("converterConfigFile");
        VirtualRobot::RuntimeEnvironment::considerKey("outputFile"); //todo: get this from parameters
        VirtualRobot::RuntimeEnvironment::processCommandLine(argc,argv);
        VirtualRobot::RuntimeEnvironment::print();

        VirtualRobot::RuntimeEnvironment::addDataPath(MMMConverter_BASE_DIR);

        //VALIDATE PARAMETERS

        if (VirtualRobot::RuntimeEnvironment::hasValue("libPath"))
        {
            std::string libpaths = VirtualRobot::RuntimeEnvironment::getValue("libPath");
            std::vector<std::string> strs;
            std::string splitStr = ";,";
            boost::split(strs, libpaths, boost::is_any_of(splitStr));
            converterLibSearchPaths.insert(converterLibSearchPaths.end(), strs.begin(), strs.end());
        }

        if (VirtualRobot::RuntimeEnvironment::hasValue("inputDataVicon"))
        {
            viconInputFile = VirtualRobot::RuntimeEnvironment::getValue("inputDataVicon");
            converterMode = eHuman2MMM;
            if (VirtualRobot::RuntimeEnvironment::hasValue("inputDataMMM"))
            {
                MMM_ERROR << "Could not continue, input data must be MMM OR VICON" << std::endl;
                return false;
            }
        }

        if (VirtualRobot::RuntimeEnvironment::hasValue("inputDataMMM"))
        {
            mmmInputFile = VirtualRobot::RuntimeEnvironment::getValue("inputDataMMM");
            converterMode = eMMM2Robot;
            if (VirtualRobot::RuntimeEnvironment::hasValue("inputDataVicon"))
            {
                MMM_ERROR << "Could not continue, input data must be MMM OR VICON" << std::endl;
                return false;
            }
        }

        if (VirtualRobot::RuntimeEnvironment::hasValue("outputModel"))
            outModelFile = VirtualRobot::RuntimeEnvironment::getValue("outputModel");

        if (VirtualRobot::RuntimeEnvironment::hasValue("inputModel"))
        {
            inModelFile = VirtualRobot::RuntimeEnvironment::getValue("inputModel");
            if (converterMode==eHuman2MMM)
            {
                MMM_ERROR << "Parameter inputModel not allowed in Human -> MMM converterMode..." << std::endl;
                return false;
            }
        }

        if (VirtualRobot::RuntimeEnvironment::hasValue("outputModelProcessor"))
        {
            modelProcessorName = VirtualRobot::RuntimeEnvironment::getValue("outputModelProcessor");
            if (converterMode==eMMM2Robot)
            {
                MMM_ERROR << "Parameter outputModelProcessor not allowed in MMM -> Robot converterMode..." << std::endl;
                return false;
            }
        }

        if (VirtualRobot::RuntimeEnvironment::hasValue("outputModelProcessorConfigFile"))
        {
            modelProcessorConfigFile = VirtualRobot::RuntimeEnvironment::getValue("outputModelProcessorConfigFile");
            if (converterMode==eMMM2Robot)
            {
                MMM_ERROR << "Parameter outputModelProcessorConfigFile not allowed in MMM -> Robot converterMode..." << std::endl;
                return false;
            }
        }

        if (VirtualRobot::RuntimeEnvironment::hasValue("converter"))
            converterName = VirtualRobot::RuntimeEnvironment::getValue("converter");

        if (VirtualRobot::RuntimeEnvironment::hasValue("converterConfigFile"))
            converterConfigFile = VirtualRobot::RuntimeEnvironment::getValue("converterConfigFile");


        if (VirtualRobot::RuntimeEnvironment::hasValue("outputFile"))
            outFile = VirtualRobot::RuntimeEnvironment::getValue("outputFile");



        //GET ABSOLUTE PATHS
        if (converterMode==eHuman2MMM && !VirtualRobot::RuntimeEnvironment::getDataFileAbsolute(viconInputFile))
        {
            MMM_ERROR << "Could not find vicon input data file" << std::endl;
            return false;
        }

        if (converterMode==eMMM2Robot && !VirtualRobot::RuntimeEnvironment::getDataFileAbsolute(mmmInputFile))
        {
            MMM_ERROR << "Could not find mmm input data file" << std::endl;
            return false;
        }

        if (!outModelFile.empty() && !VirtualRobot::RuntimeEnvironment::getDataFileAbsolute(outModelFile))
        {
            MMM_ERROR << "Could not find output mmm model file " << outModelFile << std::endl;
            return false;
        }

        if ( converterMode==eMMM2Robot && !inModelFile.empty() && !VirtualRobot::RuntimeEnvironment::getDataFileAbsolute(inModelFile) )
        {
            MMM_ERROR << "Could not find input model file " << outModelFile << std::endl;
            return false;
        }

        if (converterMode==eHuman2MMM && !outModelFile.empty() && !modelProcessorConfigFile.empty() && !VirtualRobot::RuntimeEnvironment::getDataFileAbsolute(modelProcessorConfigFile))
            MMM_WARNING << "Could not find output model processor config file:" << modelProcessorConfigFile << std::endl;

        setupConverterFactories();

        if (!converterConfigFile.empty() && !VirtualRobot::RuntimeEnvironment::getDataFileAbsolute(converterConfigFile))
            MMM_WARNING << "Could not find converter config file:" << converterConfigFile << std::endl;

        return true;
    }


    void print()
    {
        MMM_INFO << "*** MMMConverter Configuration ***" << std::endl;
        if (converterMode == eHuman2MMM)
        {
            std::cout << "Converter mode: Human->MMM" << std::endl;
            std::cout << "Output MMM model " << outModelFile << std::endl;
            std::cout << "Output ModelProcessor " << modelProcessorName << std::endl;
            std::cout << "Output ModelProcessor config file " << modelProcessorConfigFile << std::endl;
            std::cout << "Using Vicon input data " << viconInputFile << std::endl;
            std::cout << "Using Converter " << converterName << std::endl;
            std::cout << "Using Converter config file " << converterConfigFile << std::endl;
            std::cout << "Output file " << outFile << std::endl;
        }
        else
        {
            std::cout << "Converter mode: MMM->Robot" << std::endl;
            //std::cout << "Output MMM model " << mmmModelFile << std::endl;
            //std::cout << "Input ModelProcessor " << modelProcessorName << std::endl;
            //std::cout << "Input ModelProcessor config file " << modelProcessorConfigFile << std::endl;
            std::cout << "Using MMM input data " << mmmInputFile << std::endl;
            std::cout << "Using Converter " << converterName << std::endl;
            std::cout << "Using Converter config file " << converterConfigFile << std::endl;
        }
    }

    std::string converterName;				//the converter name, the same as in the factory
    std::string converterConfigFile;		//
    std::string outModelFile;				//in Human2MMM mode = MMM Model; in MMM2Robot mode = Robot Model
    std::string inModelFile;				//in
    std::string modelProcessorName;
    std::string modelProcessorConfigFile;
    std::string viconInputFile;
    std::string mmmInputFile;
    std::string outFile;

    enum ConverterMode
    {
        eHuman2MMM,
        eMMM2Robot
    };
    ConverterMode converterMode;

    std::vector<std::string> converterLibSearchPaths;

};

#endif //__MMMConverterConfiguration_H_


